import cv2
import cv2 as cv
import numpy as np
import math as m
from numba import jit  # 转换为机器代码，加速运算
from matplotlib import pyplot as plt
from PyQt5.QtWidgets import *
# from Experiment3 import Ui_Dialog as UI_Conv
import time


def edge_detect(img, deal_Type):
    """根据用户的选择，对于图像做相应的灰度增强处理"""
    if img.shape[-1] == 3:
        pass
    if deal_Type == 1:
        img = prewitt(img)
    elif deal_Type == 2:
        img = sobel(img)
    elif deal_Type == 3:
        img = log(img)
    elif deal_Type == 4:
        img = canny(img)
    return img


def prewitt(img):
    """*功能 : 根据prewitt对应的卷积模板，对图像进行边缘检测
    *注意，这里只引入水平和竖直两个方向边缘检测卷积模板"""
    time1 = time.time()  # 程序计时开始
    rows, cols = img.shape[:2]  # 获取宽和高
    new_img = np.zeros(img.shape, dtype=np.uint8)  # 新建同原图大小一致的空图像
    prw_convs = np.array([[[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]],
                          [[-1, -1, -1], [0, 0, 0], [1, 1, 1]]], dtype=int)
    # 图像遍历, 求取prewitt边缘

    '''这里写入你的程序'''
    for i in range(1, rows - 1):
        for j in range(1, cols - 1):
            # 提取3x3邻域
            region = img[i - 1:i + 2, j - 1:j + 2]

            # 应用水平方向卷积核
            gx = np.sum(region * prw_convs[0])
            # 应用垂直方向卷积核
            gy = np.sum(region * prw_convs[1])

            # 计算梯度幅值
            gradient = np.sqrt(gx ** 2 + gy ** 2)
            # 或者使用近似计算：gradient = abs(gx) + abs(gy)

            # 限制在0-255范围内
            gradient = min(255, max(0, gradient))
            new_img[i, j] = int(gradient)

    time2 = time.time()  # 程序计时结束
    print("prewitt算子边缘检测手写程序处理时间：%.3f毫秒" % ((time2 - time1) * 1000))

    # cv程序编写
    time1 = time.time()
    '''这里写入你的程序'''
    # 使用OpenCV的filter2D函数实现

    # 水平方向Prewitt卷积核
    kernel_x = np.array([[-1, 0, 1],
                         [-1, 0, 1],
                         [-1, 0, 1]], dtype=np.float32)

    # 垂直方向Prewitt卷积核
    kernel_y = np.array([[-1, -1, -1],
                         [0, 0, 0],
                         [1, 1, 1]], dtype=np.float32)

    # 分别计算水平和垂直方向的梯度
    gx_cv = cv2.filter2D(img, cv2.CV_32F, kernel_x)
    gy_cv = cv2.filter2D(img, cv2.CV_32F, kernel_y)

    # 计算梯度幅值
    gradient_cv = cv2.magnitude(gx_cv, gy_cv)

    # 转换为8位图像
    cv_result = cv2.convertScaleAbs(gradient_cv)

    time2 = time.time()  # 程序计时结束
    print("prewitt算子边缘检测CV程序处理时间：%.3f毫秒" % ((time2 - time1) * 1000))

    # return new_img
    return cv_result

def sobel(img):
    """*功能 : 根据sobel对应的卷积模板，对图像进行边缘检测
       *注意，这里只引入水平和竖直两个方向边缘检测卷积模板"""
    time1 = time.time()  # 程序计时开始
    rows, cols = img.shape[:2]  # 获取宽和高
    new_img = np.zeros(img.shape, dtype=np.uint8)  # 新建同原图大小一致的空图像
    prw_convs = np.array([[[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]],
                          [[-1, -2, -1], [0, 0, 0], [1, 2, 1]]], dtype=int)
    # 图像遍历, 求取sobel边缘

    '''这里写入你的程序'''
    for i in range(1, rows - 1):
        for j in range(1, cols - 1):
            region = img[i - 1:i + 2, j - 1:j + 2]
            gx = np.sum(region * prw_convs[0])
            gy = np.sum(region * prw_convs[1])
            gradient = np.sqrt(gx ** 2 + gy ** 2)
            gradient = min(255, max(0, gradient))
            new_img[i, j] = int(gradient)

    time2 = time.time()  # 程序计时结束
    print("sobel算子边缘检测手写程序处理时间：%.3f毫秒" % ((time2 - time1) * 1000))

    # cv程序编写

    '''这里写入你的程序'''
    time1 = time.time()
    kernel_x = np.array([[-1,0,1],[-2,0,2],[-1,0,1]], dtype=np.float32)
    kernel_y = np.array([[-1,-2,-1],[0,0,0],[1,2,1]], dtype=np.float32)

    gx_cv = cv2.filter2D(img, cv2.CV_32F, kernel_x)
    gy_cv = cv2.filter2D(img, cv2.CV_32F, kernel_y)
    gradient_cv = cv2.magnitude(gx_cv, gy_cv)

    cv_result = cv2.convertScaleAbs(gradient_cv)

    time2 = time.time()  # 程序计时结束
    print("sobel算子边缘检测CV程序处理时间：%.3f毫秒" % ((time2 - time1) * 1000))
    return new_img


def log(img):
    """*功能 : 根据LOG算子对应的卷积模板，对图像进行边缘检测"""
    time1 = time.time()  # 程序计时开始
    rows, cols = img.shape[:2]  # 获取宽和高
    new_img = np.zeros(img.shape, dtype=np.uint8)  # 新建同原图大小一致的空图像
    prw_conv = np.array([[-2, -4, -4, -4, -2], [-4, 0, 8, 0, -4], [-4, 8, 24, 8, -4],
                         [-4, 0, 8, 0, -4], [-2, -4, -4, -4, -2]], dtype=int)
    # 图像遍历, 求取LOG边缘

    '''这里写入你的程序'''

    for i in range(2, rows - 2):
        for j in range(2, cols - 2):
            region = img[i - 2:i + 3, j - 2:j + 3]
            gx = np.sum(region * prw_conv)
            gx_abs = np.abs(gx)
            new_img[i, j] = np.clip(gx_abs, 0, 255).astype(np.uint8)


    time2 = time.time()  # 程序计时结束

    print("log算子边缘检测手写程序处理时间：%.3f毫秒" % ((time2 - time1) * 1000))

    # cv程序编写

    '''这里写入你的程序'''
    time1 = time.time()
    blurred_img = cv2.GaussianBlur(img, (5, 5), 0)
    log_img_cv = cv2.Laplacian(blurred_img, cv2.CV_64F)
    log_img_cv = cv2.convertScaleAbs(log_img_cv)
    # 先取绝对值（边缘响应可能为负），再归一化
    log_abs = np.abs(log_img_cv)
    log_norm = cv2.normalize(log_abs, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)

    time2 = time.time()  # 程序计时结束
    print("log算子边缘检测CV程序处理时间：%.3f毫秒" % ((time2 - time1) * 1000))
    # return new_img
    return log_norm

def canny(img):
    """*功能 : canny算子
    *"""
    new_img = cv.Canny(img, 100, 200)

    return new_img


def otsu(img, jug):
    """*功能：大津阈值分割，求取直方图数组，根据类间方差最大原理自动选择阈值，
    *注意：只处理灰度图像"""
    time1 = time.time()  # 程序计时开始
    rows, cols = img.shape[:2]  # 获取宽和高
    new_img = np.zeros(img.shape, dtype=np.uint8)  # 新建同原图大小一致的空图像
    hist = cv.calcHist([img], [0], None, [256], [0, 255])  # 直接用cv函数计算直方图，hist: numpy格式

    '''这里写入你的程序'''
    # 计算总像素数
    total_pixels = rows * cols

    # 归一化直方图，得到每个灰度级的概率
    hist_norm = hist.ravel() / total_pixels

    # 初始化变量
    max_variance = 0
    best_threshold = 0

    # 遍历所有可能的阈值
    for t in range(0, 256):
        # 计算背景类的权重和均值
        w0 = np.sum(hist_norm[:t + 1])
        if w0 == 0:
            continue
        u0 = np.sum(np.arange(t + 1) * hist_norm[:t + 1]) / w0

        # 计算前景类的权重和均值
        w1 = np.sum(hist_norm[t + 1:])
        if w1 == 0:
            continue
        u1 = np.sum(np.arange(t + 1, 256) * hist_norm[t + 1:]) / w1

        # 计算类间方差
        between_variance = w0 * w1 * (u0 - u1) ** 2

        # 更新最大方差和最佳阈值
        if between_variance > max_variance:
            max_variance = between_variance
            best_threshold = t

    # 使用最佳阈值进行图像分割
    new_img[img > best_threshold] = 255
    new_img[img <= best_threshold] = 0

    print(f"手写大津法计算的最佳阈值: {best_threshold}")


    time2 = time.time()  # 程序计时结束
    print("大津阈值手写程序处理时间：%.3f毫秒" % ((time2 - time1) * 1000))

    # opencv大津阈值分割
    time1 = time.time()

    max_t, new_img = cv.threshold(img, 0, 255, cv.THRESH_OTSU)
    time2 = time.time()  # 程序计时结束
    print("大津阈值cv程序处理时间：%.3f毫秒" % ((time2 - time1) * 1000))

    if jug:
        plt.plot(hist, color="r", label="otsu value in histogram")
        plt.xlim([0, 256])
        plt.axvline(max_t, color='green')  # 在直方图中绘制出阈值位置
        plt.legend()  # 用于给图像加图例，各种符号和颜色所代表内容与指标的说明
        plt.show()
    return new_img


def hough_detect(img, deal_Type):
    """根据用户的选择，对于图像做相应的图像平滑处理"""
    if img.shape[-1] == 3:
        pass
    if deal_Type == 1:
        img = line_detect(img, 2)
    elif deal_Type == 2:
        img = circle_detect(img)
    return img


def hough_transform(img):
    """根据传入的图像，求取目标点对应的hough域，公式：ρ = x cos θ + y sin θ
    注：默认图像中255点为目标点"""
    rows, cols = img.shape[:2]  # 获取宽和高
    hg_rows, hg_cols = 180, int(m.sqrt(cols * cols + rows * rows))
    hough_img = np.zeros((hg_rows, hg_cols), dtype=np.int32)  # 新建hough域，全为0值


    '''这里写入你的程序'''
    # 遍历图像中的每个像素
    for y in range(rows):
        for x in range(cols):
            if img[y, x] == 255:
                # 遍历所有角度（0-179度）
                for theta in range(hg_rows):
                    # 将角度转换为弧度
                    theta_rad = np.deg2rad(theta)
                    # 计算ρ值
                    rho = int(x * m.cos(theta_rad) + y * m.sin(theta_rad))
                    # 确保ρ值在有效范围内
                    if 0 <= rho < hg_cols:
                        # 在Hough空间中投票
                        hough_img[theta, rho] += 1
    return hough_img


def line_detect(img, num):
    """*功能 : 通过hough变换检测直线，num:需检测直线的条数"""
    img = 255 - img
    time1 = time.time()  # 程序计时开始
    hough_img = hough_transform(img)
    hough_copy = hough_img.copy()
    rows, cols = img.shape[:2]  # 获取原图宽和高

    detected_lines = []  # 存储检测到的直线（ρ, θ）

    # '''这里写入你的程序'''

    # # 1. 手写霍夫直线检测：从霍夫空间中提取前num个峰值（对应最明显的直线）
    # max_rho = (hough_copy.shape[1] - 1) // 2  # 从霍夫空间列数反推max_rho
    # for _ in range(num):
    #     # 找到当前霍夫空间中的最大值位置
    #     max_val = hough_copy.max()
    #     if max_val == 0:
    #         break  # 若没有更多峰值，提前结束
    #     # 获取最大值对应的坐标(θ_deg, ρ_idx)
    #     theta_deg, rho_idx = np.where(hough_copy == max_val)
    #     theta_deg = theta_deg[0]  # 取第一个最大值的位置
    #     rho_idx = rho_idx[0]
    #     # 计算对应的ρ（还原偏移量）和θ（弧度）
    #     rho = rho_idx - max_rho
    #     theta = theta_deg * np.pi / 180
    #     detected_lines.append((rho_idx, theta))
    #     # 抑制峰值周围区域（避免重复检测同一直线）
    #     for i in range(max(0, theta_deg - 2), min(hough_copy.shape[0], theta_deg + 3)):
    #         for j in range(max(0, rho_idx - 2), min(hough_copy.shape[1], rho_idx + 3)):
    #             hough_copy[i, j] = 0
    #
    # # 绘制手写检测的直线（绿色）
    # hough_img = cv.cvtColor(img, cv.COLOR_GRAY2BGR)
    # for rho, theta in detected_lines:
    #     a = np.cos(theta)
    #     b = np.sin(theta)
    #     x0, y0 = a * rho, b * rho  # 直线上一点
    #     # 计算直线上的两个端点（确保覆盖整个图像）
    #     x1 = int(x0 + 1000 * (-b))
    #     y1 = int(y0 + 1000 * (a))
    #     x2 = int(x0 - 1000 * (-b))
    #     y2 = int(y0 - 1000 * (a))
    #     cv.line(hough_img, (x1, y1), (x2, y2), (0, 255, 0), 1)
    #
    # time2 = time.time()  # 程序计时结束
    # print("hough直线检测手写程序处理时间：%.3f毫秒" % ((time2 - time1) * 1000))

    # opencv函数检测直线
    time1 = time.time()
    lines_cv = cv.HoughLines(img, 1, np.pi / 180, 100)  # 这里对最后一个参数使用了经验型的值
    hough_img_cv = cv.cvtColor(img, cv.COLOR_GRAY2BGR)  # 转彩色，方便显示
    if lines_cv is not None:
        for line in lines_cv[:min(num, len(lines_cv))]:
            p, a = line[0]  # 第一个元素是距离rho, 第二个元素是角度theta
            pt_start = (0, int(p / m.sin(a)))  # 绘制直线起点
            pt_end = (cols, int((p - cols * m.cos(a)) / m.sin(a)))  # 绘制直线终点
            cv.line(hough_img_cv, pt_start, pt_end, (0, 0, 255), 1)
    time2 = time.time()  # 程序计时结束
    print("hough直线检测opencv程序处理时间：%.3f毫秒" % ((time2 - time1) * 1000))

    return img,hough_img_cv
    # return img, hough_img

def circle_detect(img):
    """*功能 : 直接利用opencv中的hough圆检测，检测出图像中的圆"""
    time1 = time.time()  # 程序计时开始
    # 霍夫变换圆检测

    '''这里写入你的程序'''

    # 使用霍夫圆检测
    circles = cv.HoughCircles(img, cv.HOUGH_GRADIENT, dp=1, minDist=20,
                              param1=50, param2=30, minRadius=0, maxRadius=0)

    # 创建彩色图像用于绘制检测到的圆
    new_img = cv.cvtColor(img, cv.COLOR_GRAY2BGR)

    if circles is not None:
        circles = np.uint16(np.around(circles))
        for circle in circles[0, :]:
            # 绘制圆的外围
            center = (circle[0], circle[1])
            radius = circle[2]
            cv.circle(new_img, center, radius, (0, 255, 0), 2)
            # 绘制圆心
            cv.circle(new_img, center, 2, (0, 0, 255), 3)
    time2 = time.time()  # 程序计时结束
    print("hough圆检测opencv程序处理时间：%.3f毫秒" % ((time2 - time1) * 1000))

    return new_img
